package com.lcf.system.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.baomidou.mybatisplus.extension.toolkit.Db;
import com.lcf.framework.enums.RedisLockPrefixEnum;
import com.lcf.framework.exception.SysException;
import com.lcf.framework.utils.RedisLockUtils;
import com.lcf.framework.utils.UserUtils;
import com.lcf.framework.vo.PageVO;
import com.lcf.system.dao.SysOrderDao;
import com.lcf.system.dto.OrderGoodsDTO;
import com.lcf.system.dto.SysOrderDTO;
import com.lcf.system.entity.*;
import com.lcf.system.enums.GoodsStatusEnum;
import com.lcf.system.enums.LogisticsStatusEnum;
import com.lcf.system.enums.OrderStatusEnum;
import com.lcf.system.enums.PayStatusEnum;
import com.lcf.system.query.SysOrderPageQuery;
import com.lcf.system.service.SysOrderService;
import com.lcf.system.vo.OrderGoodsVO;
import com.lcf.system.vo.SysOrderPageVO;
import com.lcf.system.vo.SysOrderVO;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 订单(SysOrder)表服务实现类
 *
 * @author lcf
 * @since 2025-05-02 11:55:03
 */
@Service("sysOrderService")
public class SysOrderServiceImpl extends ServiceImpl<SysOrderDao, SysOrder> implements SysOrderService {
    @Resource
    private RedisLockUtils redisLockUtils;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveOrder(SysOrderDTO dto) {
        return saveOrder(dto, UserUtils.getUserId(), null);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveCartOrder(List<Long> cartIdList) {
        List<GoodsCart> list = Db.lambdaQuery(GoodsCart.class).in(GoodsCart::getId, cartIdList).list();

        Long goodsId = list.get(0).getGoodsId();
        Goods goods = Db.lambdaQuery(Goods.class).eq(Goods::getId, goodsId).one();
        if (goods != null) {
            SysOrderDTO sysOrderDTO = new SysOrderDTO();
            sysOrderDTO.setStoreId(goods.getStoreId());

            sysOrderDTO.setGoodsList(list.stream().map(x -> {
                OrderGoodsDTO orderGoodsDTO = new OrderGoodsDTO();
                orderGoodsDTO.setGoodsId(x.getGoodsId());
                orderGoodsDTO.setGoodsNum(x.getGoodsNum());
                return orderGoodsDTO;
            }).toList());

            saveOrder(sysOrderDTO, UserUtils.getUserId(), null);
        }

        return Db.lambdaUpdate(GoodsCart.class).in(GoodsCart::getId, cartIdList).remove();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveGroupOrder(long ruleId) {
        Db.lambdaQuery(SysTenantRule.class).eq(SysTenantRule::getId, ruleId).oneOpt().ifPresent(sysTenantRule -> {
            if (!Objects.equals(0, sysTenantRule.getIsOrder())) {
                throw new SysException("活动已结束");
            }

            List<RuleUser> ruleUsers = Db.lambdaQuery(RuleUser.class).eq(RuleUser::getRuleId, ruleId).list();
            Goods goods = Db.lambdaQuery(Goods.class).eq(Goods::getId, sysTenantRule.getGoodsId()).one();
            RoleDetail detail = Db.lambdaQuery(RoleDetail.class).eq(RoleDetail::getId, sysTenantRule.getDetailId()).one();

            ruleUsers.forEach(ruleUser -> {
                SysOrderDTO sysOrderDTO = new SysOrderDTO();
                sysOrderDTO.setStoreId(goods.getStoreId());

                OrderGoodsDTO orderGoodsDTO = new OrderGoodsDTO();
                orderGoodsDTO.setGoodsId(goods.getId());
                orderGoodsDTO.setGoodsNum(ruleUser.getGoodsNum());
                sysOrderDTO.setGoodsList(Collections.singletonList(orderGoodsDTO));

                saveOrder(sysOrderDTO, ruleUser.getUserId(), detail);
            });

            sysTenantRule.setIsOrder(1);
            sysTenantRule.updateById();
        });

        return true;
    }

    @Override
    public PageVO<SysOrderPageVO> page(SysOrderPageQuery query) {
        Page<SysOrder> page = Db.lambdaQuery(SysOrder.class)
                .eq(SysOrder::getUserId, UserUtils.getUserId())
                .eq(StringUtils.isNotBlank(query.getOrderNo()), SysOrder::getOrderNo, query.getOrderNo())
                .eq(query.getStoreId() != null, SysOrder::getStoreId, query.getStoreId())
                .eq(query.getGoodsStatus() != null, SysOrder::getGoodsStatus, query.getGoodsStatus())
                .eq(query.getOrderStatus() != null, SysOrder::getOrderStatus, query.getOrderStatus())
                .eq(query.getLogisticsStatus() != null, SysOrder::getLogisticsStatus, query.getLogisticsStatus())
                .eq(query.getPayStatus() != null, SysOrder::getPayStatus, query.getPayStatus())
                .orderByDesc(SysOrder::getCreateTime)
                .page(query.toPage());

        if (CollectionUtils.isEmpty(page.getRecords())) {
            return new PageVO<>(page, SysOrderPageVO.class);
        }

        List<Long> orderIdList = page.getRecords().stream().map(SysOrder::getId).toList();
        List<OrderGoods> goodsList = Db.lambdaQuery(OrderGoods.class).in(OrderGoods::getOrderId, orderIdList).list();

        PageVO<SysOrderPageVO> result = new PageVO<>(page, SysOrderPageVO.class);
        result.getList().forEach(order -> {
            List<OrderGoods> list = goodsList.stream().filter(goods -> goods.getOrderId().equals(order.getId())).toList();
            order.setGoodsList(BeanUtil.copyToList(list, OrderGoodsVO.class));
        });

        return result;
    }

    @Override
    public PageVO<SysOrderPageVO> pageByFarmer(SysOrderPageQuery query) {
        List<Long> storeIdList = Db.lambdaQuery(Store.class).eq(Store::getUserId, UserUtils.getUserId()).list().stream().map(Store::getId).toList();
        if (CollectionUtils.isEmpty(storeIdList)) {
            return new PageVO<>();
        }

        Page<SysOrder> page = Db.lambdaQuery(SysOrder.class)
                .in(SysOrder::getStoreId, storeIdList)
                .eq(StringUtils.isNotBlank(query.getOrderNo()), SysOrder::getOrderNo, query.getOrderNo())
                .eq(query.getStoreId() != null, SysOrder::getStoreId, query.getStoreId())
                .eq(query.getGoodsStatus() != null, SysOrder::getGoodsStatus, query.getGoodsStatus())
                .eq(query.getOrderStatus() != null, SysOrder::getOrderStatus, query.getOrderStatus())
                .eq(query.getLogisticsStatus() != null, SysOrder::getLogisticsStatus, query.getLogisticsStatus())
                .eq(query.getPayStatus() != null, SysOrder::getPayStatus, query.getPayStatus())
                .orderByDesc(SysOrder::getCreateTime)
                .page(query.toPage());

        if (CollectionUtils.isEmpty(page.getRecords())) {
            return new PageVO<>(page, SysOrderPageVO.class);
        }

        List<Long> orderIdList = page.getRecords().stream().map(SysOrder::getId).toList();
        List<OrderGoods> goodsList = Db.lambdaQuery(OrderGoods.class).in(OrderGoods::getOrderId, orderIdList).list();

        PageVO<SysOrderPageVO> result = new PageVO<>(page, SysOrderPageVO.class);
        result.getList().forEach(order -> {
            List<OrderGoods> list = goodsList.stream().filter(goods -> goods.getOrderId().equals(order.getId())).toList();
            order.setGoodsList(BeanUtil.copyToList(list, OrderGoodsVO.class));
        });

        return result;
    }
    @Override
    public PageVO<SysOrderPageVO> pageByLeader(SysOrderPageQuery query) {

        Page<SysOrder> page = Db.lambdaQuery(SysOrder.class)
                .eq(StringUtils.isNotBlank(query.getOrderNo()), SysOrder::getOrderNo, query.getOrderNo())
                .orderByDesc(SysOrder::getCreateTime)
                .page(query.toPage());

        if (CollectionUtils.isEmpty(page.getRecords())) {
            return new PageVO<>(page, SysOrderPageVO.class);
        }

        List<Long> orderIdList = page.getRecords().stream().map(SysOrder::getId).toList();
        List<OrderGoods> goodsList = Db.lambdaQuery(OrderGoods.class).in(OrderGoods::getOrderId, orderIdList).list();

        PageVO<SysOrderPageVO> result = new PageVO<>(page, SysOrderPageVO.class);
        result.getList().forEach(order -> {
            List<OrderGoods> list = goodsList.stream().filter(goods -> goods.getOrderId().equals(order.getId())).toList();
            order.setGoodsList(BeanUtil.copyToList(list, OrderGoodsVO.class));
        });

        return result;
    }

    @Override
    public SysOrderVO getDetail(long id) {
        SysOrder sysOrder = getById(id);
        if (sysOrder == null) {
            throw new SysException("订单不存在");
        }

        SysOrderVO result = BeanUtil.copyProperties(sysOrder, SysOrderVO.class);

        List<OrderGoods> goodsList = Db.lambdaQuery(OrderGoods.class).eq(OrderGoods::getOrderId, id).list();
        result.setGoodsList(BeanUtil.copyToList(goodsList, OrderGoodsVO.class));

        return result;
    }

    @Override
    public SysOrderVO getByOrderNo(String orderNo) {
        SysOrder sysOrder = Db.lambdaQuery(SysOrder.class).eq(SysOrder::getOrderNo, orderNo).one();
        if (sysOrder == null) {
            throw new SysException("订单不存在");
        }

        SysOrderVO result = BeanUtil.copyProperties(sysOrder, SysOrderVO.class);

        List<OrderGoods> goodsList = Db.lambdaQuery(OrderGoods.class).eq(OrderGoods::getOrderNo, orderNo).list();
        result.setGoodsList(BeanUtil.copyToList(goodsList, OrderGoodsVO.class));

        return result;
    }

    @Override
    public boolean stocking(long id) {
        return Db.lambdaUpdate(SysOrder.class)
                .eq(SysOrder::getId, id)
                .eq(SysOrder::getGoodsStatus, GoodsStatusEnum.WAIT_STOCK.getCode())
                .set(SysOrder::getGoodsStatus, GoodsStatusEnum.STOCKING.getCode())
                .update();
    }

    @Override
    public boolean pickUp(long id) {
        return Db.lambdaUpdate(SysOrder.class)
                .eq(SysOrder::getId, id)
                .eq(SysOrder::getGoodsStatus, GoodsStatusEnum.STOCKING.getCode())
                .set(SysOrder::getGoodsStatus, GoodsStatusEnum.WAIT_PICKUP.getCode())
                .update();
    }

    @Override
    public boolean pickedUp(long id) {
        return Db.lambdaUpdate(SysOrder.class)
                .eq(SysOrder::getId, id)
                .eq(SysOrder::getGoodsStatus, GoodsStatusEnum.WAIT_PICKUP.getCode())
                .set(SysOrder::getGoodsStatus, GoodsStatusEnum.PICKED_UP.getCode())
                .set(SysOrder::getLogisticsStatus, LogisticsStatusEnum.DELIVERY.getCode())
                .update();
    }

    @Override
    public boolean cancel(long id) {
        return Db.lambdaUpdate(SysOrder.class)
                .eq(SysOrder::getId, id)
                .eq(SysOrder::getOrderStatus, OrderStatusEnum.ORDER.getCode())
                .set(SysOrder::getOrderStatus, OrderStatusEnum.CANCELED.getCode())
                .update();
    }

    @Override
    public boolean back(long id) {
        boolean update = Db.lambdaUpdate(SysOrder.class)
                .eq(SysOrder::getId, id)
                .in(SysOrder::getLogisticsStatus, Arrays.asList(LogisticsStatusEnum.DELIVERY.getCode(), LogisticsStatusEnum.DELIVERED.getCode()))
                .set(SysOrder::getOrderStatus, OrderStatusEnum.BACK.getCode())
                .update();
        if (!update) {
            return false;
        }

        SysOrder sysOrder = Db.lambdaQuery(SysOrder.class).eq(SysOrder::getId, id).one();
        Long storeId = sysOrder.getStoreId();
        Db.lambdaQuery(Store.class).eq(Store::getId, storeId).oneOpt().ifPresent(store -> {
            new PriceRecord(store.getUserId(), sysOrder.getRealPrice().negate(), sysOrder.getUserId(), sysOrder.getOrderNo()).insert();

            redisLockUtils.tryLock(RedisLockPrefixEnum.SAVE_TOTAL_PRICE, store.getUserId(), () -> {
                TotalPrice totalPrice = Db.lambdaQuery(TotalPrice.class).eq(TotalPrice::getUserId, store.getUserId()).one();
                if (totalPrice == null) {
                    new TotalPrice(store.getUserId(), sysOrder.getRealPrice().negate()).insert();
                } else {
                    totalPrice.setTotalPrice(totalPrice.getTotalPrice().add(sysOrder.getRealPrice().negate()));
                    totalPrice.updateById();
                }
            });
        });

        return true;
    }

    @Override
    public boolean delivery(long id) {
        return Db.lambdaUpdate(SysOrder.class)
                .eq(SysOrder::getId, id)
                .eq(SysOrder::getLogisticsStatus, LogisticsStatusEnum.WAIT_PICKUP.getCode())
                .set(SysOrder::getLogisticsStatus, LogisticsStatusEnum.DELIVERY.getCode())
                .update();
    }

    @Override
    public boolean delivered(long id) {
        return Db.lambdaUpdate(SysOrder.class)
                .eq(SysOrder::getId, id)
                .eq(SysOrder::getLogisticsStatus, LogisticsStatusEnum.DELIVERY.getCode())
                .set(SysOrder::getLogisticsStatus, LogisticsStatusEnum.DELIVERED.getCode())
                .update();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean pay(long id) {
        boolean update = Db.lambdaUpdate(SysOrder.class)
                .eq(SysOrder::getId, id)
                .eq(SysOrder::getPayStatus, PayStatusEnum.UNPAID.getCode())
                .set(SysOrder::getPayStatus, PayStatusEnum.PAID.getCode())
                .update();
        if (!update) {
            return false;
        }

        SysOrder sysOrder = Db.lambdaQuery(SysOrder.class).eq(SysOrder::getId, id).one();
        Long storeId = sysOrder.getStoreId();
        Db.lambdaQuery(Store.class).eq(Store::getId, storeId).oneOpt().ifPresent(store -> {
            new PriceRecord(store.getUserId(), sysOrder.getRealPrice(), sysOrder.getUserId(), sysOrder.getOrderNo()).insert();

            redisLockUtils.tryLock(RedisLockPrefixEnum.SAVE_TOTAL_PRICE, store.getUserId(), () -> {
                TotalPrice totalPrice = Db.lambdaQuery(TotalPrice.class).eq(TotalPrice::getUserId, store.getUserId()).one();
                if (totalPrice == null) {
                    new TotalPrice(store.getUserId(), sysOrder.getRealPrice()).insert();
                } else {
                    totalPrice.setTotalPrice(totalPrice.getTotalPrice().add(sysOrder.getRealPrice()));
                    totalPrice.updateById();
                }
            });
        });

        return true;
    }

    private boolean saveOrder(SysOrderDTO dto, long userId, RoleDetail detail) {
        SysOrder sysOrder = BeanUtil.copyProperties(dto, SysOrder.class);
        sysOrder.setOrderNo(RandomUtil.randomString(32));
        sysOrder.setUserId(userId);

        List<Long> goodsIdList = dto.getGoodsList().stream().map(OrderGoodsDTO::getGoodsId).distinct().toList();
        Map<Long, Goods> goodsMap = Db.lambdaQuery(Goods.class).in(Goods::getId, goodsIdList).list()
                .stream().collect(Collectors.toMap(Goods::getId, x -> x));

        BigDecimal totalPrice = dto.getGoodsList().stream()
                .map(x -> BigDecimal.valueOf(x.getGoodsNum()).multiply(goodsMap.get(x.getGoodsId()).getGoodsPrice()))
                .reduce(BigDecimal::add).orElse(BigDecimal.ZERO);

        sysOrder.setOrderPrice(totalPrice);
        sysOrder.setRealPrice(totalPrice);

        if (detail != null) {
            BigDecimal realPrice = dto.getGoodsList().stream()
                    .map(x -> BigDecimal.valueOf(x.getGoodsNum()).multiply(detail.getDiscount()))
                    .reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
            sysOrder.setRealPrice(realPrice);
        }

        sysOrder.setGoodsStatus(GoodsStatusEnum.WAIT_STOCK.getCode());
        sysOrder.setOrderStatus(OrderStatusEnum.ORDER.getCode());
        sysOrder.setLogisticsStatus(LogisticsStatusEnum.WAIT_PICKUP.getCode());
        sysOrder.setPayStatus(PayStatusEnum.UNPAID.getCode());
        sysOrder.setOrderTime(LocalDateTime.now());
        sysOrder.insert();

        dto.getGoodsList().forEach(goods -> new OrderGoods(sysOrder, goods, goodsMap, detail).insert());

        return true;
    }
}

